{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#   使用GoogLeNet模型—水果\n",
    "\n",
    "本例对应的文件结构如下"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 代码文件说明： \n",
    "> 1. data_partitioning.py       - 数据集划分脚本（9:1比例分割训练/测试集）\n",
    "> 2. mean_std.py                - 数据标准化处理  (训练代码会用到计算结果，可以不运行)\n",
    "> 3. model.py                   - GoogLeNet网络模型定义\n",
    "> 4. model_train.py             - 模型训练与验证流程\n",
    "> 5. model_test.py              - 模型测试与推理演示\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  数据集目录结构\n",
    "<pre>\n",
    "原始数据集结构：\n",
    "fruits/\n",
    "    ├── apple/\n",
    "    │   ├── 1.jpg\n",
    "    │   └── ...\n",
    "    └── banana/\n",
    "    │   ├── 1.jpg\n",
    "    │   └── ...\n",
    "    │   ├── 1.jpg\n",
    "    │   └── ...\n",
    "    └── grape/\n",
    "    │   ├── 1.jpg\n",
    "    │   └── ...\n",
    "    │   ├── 1.jpg\n",
    "    │   └── ...\n",
    "    .......\n",
    "</pre>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 运行data_partitioning.py划分数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[apple] processing [334/334]\n",
      "[banana] processing [325/325]\n",
      "[grape] processing [323/323]\n",
      "[orange] processing [354/354]\n",
      "[pear] processing [348/348]\n",
      "processing done!\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from shutil import copy\n",
    "import random\n",
    "\n",
    "\n",
    "def mkfile(file):\n",
    "    if not os.path.exists(file):\n",
    "        os.makedirs(file)\n",
    "\n",
    "\n",
    "# 获取data文件夹下所有文件夹名（即需要分类的类名）\n",
    "file_path = 'fruits'\n",
    "flower_class = [cla for cla in os.listdir(file_path)]\n",
    "\n",
    "# 创建 训练集train 文件夹，并由类名在其目录下创建5个子目录\n",
    "mkfile('data/train')\n",
    "for cla in flower_class:\n",
    "    mkfile('data/train/' + cla)\n",
    "\n",
    "# 创建 验证集val 文件夹，并由类名在其目录下创建子目录\n",
    "mkfile('data/test')\n",
    "for cla in flower_class:\n",
    "    mkfile('data/test/' + cla)\n",
    "\n",
    "# 划分比例，训练集 : 测试集 = 9 : 1\n",
    "split_rate = 0.1\n",
    "\n",
    "# 遍历所有类别的全部图像并按比例分成训练集和验证集\n",
    "for cla in flower_class:\n",
    "    cla_path = file_path + '/' + cla + '/'  # 某一类别的子目录\n",
    "    images = os.listdir(cla_path)  # iamges 列表存储了该目录下所有图像的名称\n",
    "    num = len(images)\n",
    "    eval_index = random.sample(images, k=int(num * split_rate))  # 从images列表中随机抽取 k 个图像名称\n",
    "    for index, image in enumerate(images):\n",
    "        # eval_index 中保存验证集val的图像名称\n",
    "        if image in eval_index:\n",
    "            image_path = cla_path + image\n",
    "            new_path = 'data/test/' + cla\n",
    "            copy(image_path, new_path)  # 将选中的图像复制到新路径\n",
    "\n",
    "        # 其余的图像保存在训练集train中\n",
    "        else:\n",
    "            image_path = cla_path + image\n",
    "            new_path = 'data/train/' + cla\n",
    "            copy(image_path, new_path)\n",
    "        print(\"\\r[{}] processing [{}/{}]\".format(cla, index + 1, num), end=\"\")  # processing bar\n",
    "    print()\n",
    "\n",
    "print(\"processing done!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 运行model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------------------\n",
      "        Layer (type)               Output Shape         Param #\n",
      "================================================================\n",
      "            Conv2d-1         [-1, 64, 112, 112]           9,472\n",
      "              ReLU-2         [-1, 64, 112, 112]               0\n",
      "         MaxPool2d-3           [-1, 64, 56, 56]               0\n",
      "            Conv2d-4           [-1, 64, 56, 56]           4,160\n",
      "              ReLU-5           [-1, 64, 56, 56]               0\n",
      "            Conv2d-6          [-1, 192, 56, 56]         110,784\n",
      "              ReLU-7          [-1, 192, 56, 56]               0\n",
      "         MaxPool2d-8          [-1, 192, 28, 28]               0\n",
      "            Conv2d-9           [-1, 64, 28, 28]          12,352\n",
      "             ReLU-10           [-1, 64, 28, 28]               0\n",
      "           Conv2d-11           [-1, 96, 28, 28]          18,528\n",
      "             ReLU-12           [-1, 96, 28, 28]               0\n",
      "           Conv2d-13          [-1, 128, 28, 28]         110,720\n",
      "             ReLU-14          [-1, 128, 28, 28]               0\n",
      "           Conv2d-15           [-1, 16, 28, 28]           3,088\n",
      "             ReLU-16           [-1, 16, 28, 28]               0\n",
      "           Conv2d-17           [-1, 32, 28, 28]          12,832\n",
      "             ReLU-18           [-1, 32, 28, 28]               0\n",
      "        MaxPool2d-19          [-1, 192, 28, 28]               0\n",
      "           Conv2d-20           [-1, 32, 28, 28]           6,176\n",
      "             ReLU-21           [-1, 32, 28, 28]               0\n",
      "        Inception-22          [-1, 256, 28, 28]               0\n",
      "           Conv2d-23          [-1, 128, 28, 28]          32,896\n",
      "             ReLU-24          [-1, 128, 28, 28]               0\n",
      "           Conv2d-25          [-1, 128, 28, 28]          32,896\n",
      "             ReLU-26          [-1, 128, 28, 28]               0\n",
      "           Conv2d-27          [-1, 192, 28, 28]         221,376\n",
      "             ReLU-28          [-1, 192, 28, 28]               0\n",
      "           Conv2d-29           [-1, 32, 28, 28]           8,224\n",
      "             ReLU-30           [-1, 32, 28, 28]               0\n",
      "           Conv2d-31           [-1, 96, 28, 28]          76,896\n",
      "             ReLU-32           [-1, 96, 28, 28]               0\n",
      "        MaxPool2d-33          [-1, 256, 28, 28]               0\n",
      "           Conv2d-34           [-1, 64, 28, 28]          16,448\n",
      "             ReLU-35           [-1, 64, 28, 28]               0\n",
      "        Inception-36          [-1, 480, 28, 28]               0\n",
      "        MaxPool2d-37          [-1, 480, 14, 14]               0\n",
      "           Conv2d-38          [-1, 192, 14, 14]          92,352\n",
      "             ReLU-39          [-1, 192, 14, 14]               0\n",
      "           Conv2d-40           [-1, 96, 14, 14]          46,176\n",
      "             ReLU-41           [-1, 96, 14, 14]               0\n",
      "           Conv2d-42          [-1, 208, 14, 14]         179,920\n",
      "             ReLU-43          [-1, 208, 14, 14]               0\n",
      "           Conv2d-44           [-1, 16, 14, 14]           7,696\n",
      "             ReLU-45           [-1, 16, 14, 14]               0\n",
      "           Conv2d-46           [-1, 48, 14, 14]          19,248\n",
      "             ReLU-47           [-1, 48, 14, 14]               0\n",
      "        MaxPool2d-48          [-1, 480, 14, 14]               0\n",
      "           Conv2d-49           [-1, 64, 14, 14]          30,784\n",
      "             ReLU-50           [-1, 64, 14, 14]               0\n",
      "        Inception-51          [-1, 512, 14, 14]               0\n",
      "           Conv2d-52          [-1, 160, 14, 14]          82,080\n",
      "             ReLU-53          [-1, 160, 14, 14]               0\n",
      "           Conv2d-54          [-1, 112, 14, 14]          57,456\n",
      "             ReLU-55          [-1, 112, 14, 14]               0\n",
      "           Conv2d-56          [-1, 224, 14, 14]         226,016\n",
      "             ReLU-57          [-1, 224, 14, 14]               0\n",
      "           Conv2d-58           [-1, 24, 14, 14]          12,312\n",
      "             ReLU-59           [-1, 24, 14, 14]               0\n",
      "           Conv2d-60           [-1, 64, 14, 14]          38,464\n",
      "             ReLU-61           [-1, 64, 14, 14]               0\n",
      "        MaxPool2d-62          [-1, 512, 14, 14]               0\n",
      "           Conv2d-63           [-1, 64, 14, 14]          32,832\n",
      "             ReLU-64           [-1, 64, 14, 14]               0\n",
      "        Inception-65          [-1, 512, 14, 14]               0\n",
      "           Conv2d-66          [-1, 128, 14, 14]          65,664\n",
      "             ReLU-67          [-1, 128, 14, 14]               0\n",
      "           Conv2d-68          [-1, 128, 14, 14]          65,664\n",
      "             ReLU-69          [-1, 128, 14, 14]               0\n",
      "           Conv2d-70          [-1, 256, 14, 14]         295,168\n",
      "             ReLU-71          [-1, 256, 14, 14]               0\n",
      "           Conv2d-72           [-1, 24, 14, 14]          12,312\n",
      "             ReLU-73           [-1, 24, 14, 14]               0\n",
      "           Conv2d-74           [-1, 64, 14, 14]          38,464\n",
      "             ReLU-75           [-1, 64, 14, 14]               0\n",
      "        MaxPool2d-76          [-1, 512, 14, 14]               0\n",
      "           Conv2d-77           [-1, 64, 14, 14]          32,832\n",
      "             ReLU-78           [-1, 64, 14, 14]               0\n",
      "        Inception-79          [-1, 512, 14, 14]               0\n",
      "           Conv2d-80          [-1, 112, 14, 14]          57,456\n",
      "             ReLU-81          [-1, 112, 14, 14]               0\n",
      "           Conv2d-82          [-1, 128, 14, 14]          65,664\n",
      "             ReLU-83          [-1, 128, 14, 14]               0\n",
      "           Conv2d-84          [-1, 288, 14, 14]         332,064\n",
      "             ReLU-85          [-1, 288, 14, 14]               0\n",
      "           Conv2d-86           [-1, 32, 14, 14]          16,416\n",
      "             ReLU-87           [-1, 32, 14, 14]               0\n",
      "           Conv2d-88           [-1, 64, 14, 14]          51,264\n",
      "             ReLU-89           [-1, 64, 14, 14]               0\n",
      "        MaxPool2d-90          [-1, 512, 14, 14]               0\n",
      "           Conv2d-91           [-1, 64, 14, 14]          32,832\n",
      "             ReLU-92           [-1, 64, 14, 14]               0\n",
      "        Inception-93          [-1, 528, 14, 14]               0\n",
      "           Conv2d-94          [-1, 256, 14, 14]         135,424\n",
      "             ReLU-95          [-1, 256, 14, 14]               0\n",
      "           Conv2d-96          [-1, 160, 14, 14]          84,640\n",
      "             ReLU-97          [-1, 160, 14, 14]               0\n",
      "           Conv2d-98          [-1, 320, 14, 14]         461,120\n",
      "             ReLU-99          [-1, 320, 14, 14]               0\n",
      "          Conv2d-100           [-1, 32, 14, 14]          16,928\n",
      "            ReLU-101           [-1, 32, 14, 14]               0\n",
      "          Conv2d-102          [-1, 128, 14, 14]         102,528\n",
      "            ReLU-103          [-1, 128, 14, 14]               0\n",
      "       MaxPool2d-104          [-1, 528, 14, 14]               0\n",
      "          Conv2d-105          [-1, 128, 14, 14]          67,712\n",
      "            ReLU-106          [-1, 128, 14, 14]               0\n",
      "       Inception-107          [-1, 832, 14, 14]               0\n",
      "       MaxPool2d-108            [-1, 832, 7, 7]               0\n",
      "          Conv2d-109            [-1, 256, 7, 7]         213,248\n",
      "            ReLU-110            [-1, 256, 7, 7]               0\n",
      "          Conv2d-111            [-1, 160, 7, 7]         133,280\n",
      "            ReLU-112            [-1, 160, 7, 7]               0\n",
      "          Conv2d-113            [-1, 320, 7, 7]         461,120\n",
      "            ReLU-114            [-1, 320, 7, 7]               0\n",
      "          Conv2d-115             [-1, 32, 7, 7]          26,656\n",
      "            ReLU-116             [-1, 32, 7, 7]               0\n",
      "          Conv2d-117            [-1, 128, 7, 7]         102,528\n",
      "            ReLU-118            [-1, 128, 7, 7]               0\n",
      "       MaxPool2d-119            [-1, 832, 7, 7]               0\n",
      "          Conv2d-120            [-1, 128, 7, 7]         106,624\n",
      "            ReLU-121            [-1, 128, 7, 7]               0\n",
      "       Inception-122            [-1, 832, 7, 7]               0\n",
      "          Conv2d-123            [-1, 384, 7, 7]         319,872\n",
      "            ReLU-124            [-1, 384, 7, 7]               0\n",
      "          Conv2d-125            [-1, 192, 7, 7]         159,936\n",
      "            ReLU-126            [-1, 192, 7, 7]               0\n",
      "          Conv2d-127            [-1, 384, 7, 7]         663,936\n",
      "            ReLU-128            [-1, 384, 7, 7]               0\n",
      "          Conv2d-129             [-1, 48, 7, 7]          39,984\n",
      "            ReLU-130             [-1, 48, 7, 7]               0\n",
      "          Conv2d-131            [-1, 128, 7, 7]         153,728\n",
      "            ReLU-132            [-1, 128, 7, 7]               0\n",
      "       MaxPool2d-133            [-1, 832, 7, 7]               0\n",
      "          Conv2d-134            [-1, 128, 7, 7]         106,624\n",
      "            ReLU-135            [-1, 128, 7, 7]               0\n",
      "       Inception-136           [-1, 1024, 7, 7]               0\n",
      "AdaptiveAvgPool2d-137           [-1, 1024, 1, 1]               0\n",
      "         Flatten-138                 [-1, 1024]               0\n",
      "          Linear-139                    [-1, 5]           5,125\n",
      "================================================================\n",
      "Total params: 5,928,997\n",
      "Trainable params: 5,928,997\n",
      "Non-trainable params: 0\n",
      "----------------------------------------------------------------\n",
      "Input size (MB): 0.57\n",
      "Forward/backward pass size (MB): 69.44\n",
      "Params size (MB): 22.62\n",
      "Estimated Total Size (MB): 92.63\n",
      "----------------------------------------------------------------\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torchsummary import summary\n",
    "\n",
    "\n",
    "\n",
    "class Inception(nn.Module):\n",
    "    def __init__(self, in_channels, c1, c2, c3, c4):\n",
    "        super(Inception, self).__init__()\n",
    "        self.ReLU = nn.ReLU()\n",
    "\n",
    "        # 路线1，单1×1卷积层\n",
    "        self.p1_1 = nn.Conv2d(in_channels=in_channels, out_channels=c1, kernel_size=1)\n",
    "\n",
    "        # 路线2，1×1卷积层, 3×3的卷积\n",
    "        self.p2_1 = nn.Conv2d(in_channels=in_channels, out_channels=c2[0], kernel_size=1)\n",
    "        self.p2_2 = nn.Conv2d(in_channels=c2[0], out_channels=c2[1], kernel_size=3, padding=1)\n",
    "\n",
    "        # 路线3，1×1卷积层, 5×5的卷积\n",
    "        self.p3_1 = nn.Conv2d(in_channels=in_channels, out_channels=c3[0], kernel_size=1)\n",
    "        self.p3_2 = nn.Conv2d(in_channels=c3[0], out_channels=c3[1], kernel_size=5, padding=2)\n",
    "\n",
    "        # 路线4，3×3的最大池化, 1×1的卷积\n",
    "        self.p4_1 = nn.MaxPool2d(kernel_size=3, padding=1, stride=1)\n",
    "        self.p4_2 = nn.Conv2d(in_channels=in_channels, out_channels=c4, kernel_size=1)\n",
    "\n",
    "\n",
    "\n",
    "    def forward(self, x):\n",
    "        p1 = self.ReLU(self.p1_1(x))\n",
    "        p2 = self.ReLU(self.p2_2(self.ReLU(self.p2_1(x))))\n",
    "        p3 = self.ReLU(self.p3_2(self.ReLU(self.p3_1(x))))\n",
    "        p4 = self.ReLU(self.p4_2(self.p4_1(x)))\n",
    "        return torch.cat((p1, p2, p3, p4), dim=1)\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "class GoogLeNet(nn.Module):\n",
    "    def __init__(self, Inception):\n",
    "        super(GoogLeNet, self).__init__()\n",
    "        self.b1 = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3),\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))\n",
    "\n",
    "        self.b2 = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=1),\n",
    "            nn.ReLU(),\n",
    "            nn.Conv2d(in_channels=64, out_channels=192, kernel_size=3, padding=1),\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))\n",
    "\n",
    "        self.b3 = nn.Sequential(\n",
    "            Inception(192, 64, (96, 128), (16, 32), 32),\n",
    "            Inception(256, 128, (128, 192), (32, 96), 64),\n",
    "            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))\n",
    "\n",
    "        self.b4 = nn.Sequential(\n",
    "            Inception(480, 192, (96, 208), (16, 48), 64),\n",
    "            Inception(512, 160, (112, 224), (24, 64), 64),\n",
    "            Inception(512, 128, (128, 256), (24, 64), 64),\n",
    "            Inception(512, 112, (128, 288), (32, 64), 64),\n",
    "            Inception(528, 256, (160, 320), (32, 128), 128),\n",
    "            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))\n",
    "\n",
    "        self.b5 = nn.Sequential(\n",
    "            Inception(832, 256, (160, 320), (32, 128), 128),\n",
    "            Inception(832, 384, (192, 384), (48, 128), 128),\n",
    "            nn.AdaptiveAvgPool2d((1, 1)),\n",
    "            nn.Flatten(),\n",
    "            nn.Linear(1024, 5))\n",
    "\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Conv2d):\n",
    "                nn.init.kaiming_normal_(m.weight, mode=\"fan_out\", nonlinearity='relu')\n",
    "                if m.bias is not None:\n",
    "                    nn.init.constant_(m.bias, 0)\n",
    "\n",
    "                elif isinstance(m, nn.Linear):\n",
    "                    nn.init.normal_(m.weight, 0, 0.01)\n",
    "                    if m.bias is not None:\n",
    "                        nn.init.constant_(m.bias, 0)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.b1(x)\n",
    "        x = self.b2(x)\n",
    "        x = self.b3(x)\n",
    "        x = self.b4(x)\n",
    "        x = self.b5(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "    model = GoogLeNet(Inception).to(device)\n",
    "    print(summary(model, (3, 224, 224)))\n",
    "\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 运行model_train.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0/49\n",
      "----------\n",
      "0 train loss:54.4256 train acc: 0.2224\n",
      "0 val loss:1.4422 val acc: 0.5395\n",
      "训练和验证耗费的时间0m15s\n",
      "Epoch 1/49\n",
      "----------\n",
      "1 train loss:1.0717 train acc: 0.5914\n",
      "1 val loss:0.8719 val acc: 0.6316\n",
      "训练和验证耗费的时间0m30s\n",
      "Epoch 2/49\n",
      "----------\n",
      "2 train loss:0.7582 train acc: 0.6944\n",
      "2 val loss:0.8403 val acc: 0.7171\n",
      "训练和验证耗费的时间0m44s\n",
      "Epoch 3/49\n",
      "----------\n",
      "3 train loss:0.6497 train acc: 0.7405\n",
      "3 val loss:0.6458 val acc: 0.6842\n",
      "训练和验证耗费的时间0m59s\n",
      "Epoch 4/49\n",
      "----------\n",
      "4 train loss:0.5442 train acc: 0.7974\n",
      "4 val loss:0.4884 val acc: 0.8257\n",
      "训练和验证耗费的时间1m13s\n",
      "Epoch 5/49\n",
      "----------\n",
      "5 train loss:0.4587 train acc: 0.8287\n",
      "5 val loss:0.4547 val acc: 0.8092\n",
      "训练和验证耗费的时间1m27s\n",
      "Epoch 6/49\n",
      "----------\n",
      "6 train loss:0.4278 train acc: 0.8418\n",
      "6 val loss:0.5204 val acc: 0.7928\n",
      "训练和验证耗费的时间1m41s\n",
      "Epoch 7/49\n",
      "----------\n",
      "7 train loss:0.4197 train acc: 0.8443\n",
      "7 val loss:0.3501 val acc: 0.8717\n",
      "训练和验证耗费的时间1m55s\n",
      "Epoch 8/49\n",
      "----------\n",
      "8 train loss:0.3749 train acc: 0.8666\n",
      "8 val loss:0.5708 val acc: 0.8125\n",
      "训练和验证耗费的时间2m9s\n",
      "Epoch 9/49\n",
      "----------\n",
      "9 train loss:0.3406 train acc: 0.8731\n",
      "9 val loss:0.3540 val acc: 0.8618\n",
      "训练和验证耗费的时间2m23s\n",
      "Epoch 10/49\n",
      "----------\n",
      "10 train loss:0.3300 train acc: 0.8822\n",
      "10 val loss:0.4902 val acc: 0.8059\n",
      "训练和验证耗费的时间2m38s\n",
      "Epoch 11/49\n",
      "----------\n",
      "11 train loss:0.5286 train acc: 0.8081\n",
      "11 val loss:0.4684 val acc: 0.8191\n",
      "训练和验证耗费的时间2m53s\n",
      "Epoch 12/49\n",
      "----------\n",
      "12 train loss:0.3519 train acc: 0.8723\n",
      "12 val loss:0.3746 val acc: 0.8553\n",
      "训练和验证耗费的时间3m7s\n",
      "Epoch 13/49\n",
      "----------\n",
      "13 train loss:0.3234 train acc: 0.8888\n",
      "13 val loss:0.3604 val acc: 0.8684\n",
      "训练和验证耗费的时间3m21s\n",
      "Epoch 14/49\n",
      "----------\n",
      "14 train loss:0.3368 train acc: 0.8822\n",
      "14 val loss:0.4812 val acc: 0.8388\n",
      "训练和验证耗费的时间3m35s\n",
      "Epoch 15/49\n",
      "----------\n",
      "15 train loss:0.3147 train acc: 0.8863\n",
      "15 val loss:0.3608 val acc: 0.8750\n",
      "训练和验证耗费的时间3m49s\n",
      "Epoch 16/49\n",
      "----------\n",
      "16 train loss:0.3125 train acc: 0.8855\n",
      "16 val loss:0.3875 val acc: 0.8586\n",
      "训练和验证耗费的时间4m3s\n",
      "Epoch 17/49\n",
      "----------\n",
      "17 train loss:0.2432 train acc: 0.9168\n",
      "17 val loss:0.3533 val acc: 0.8783\n",
      "训练和验证耗费的时间4m17s\n",
      "Epoch 18/49\n",
      "----------\n",
      "18 train loss:0.2875 train acc: 0.8954\n",
      "18 val loss:0.3248 val acc: 0.8783\n",
      "训练和验证耗费的时间4m31s\n",
      "Epoch 19/49\n",
      "----------\n",
      "19 train loss:0.2410 train acc: 0.9168\n",
      "19 val loss:0.3010 val acc: 0.8914\n",
      "训练和验证耗费的时间4m45s\n",
      "Epoch 20/49\n",
      "----------\n",
      "20 train loss:0.3001 train acc: 0.8921\n",
      "20 val loss:0.3308 val acc: 0.8783\n",
      "训练和验证耗费的时间4m59s\n",
      "Epoch 21/49\n",
      "----------\n",
      "21 train loss:0.2316 train acc: 0.9217\n",
      "21 val loss:0.3328 val acc: 0.8717\n",
      "训练和验证耗费的时间5m13s\n",
      "Epoch 22/49\n",
      "----------\n",
      "22 train loss:0.1878 train acc: 0.9341\n",
      "22 val loss:0.4459 val acc: 0.8586\n",
      "训练和验证耗费的时间5m28s\n",
      "Epoch 23/49\n",
      "----------\n",
      "23 train loss:0.4319 train acc: 0.8501\n",
      "23 val loss:0.4236 val acc: 0.8421\n",
      "训练和验证耗费的时间5m42s\n",
      "Epoch 24/49\n",
      "----------\n",
      "24 train loss:0.2922 train acc: 0.8946\n",
      "24 val loss:0.4107 val acc: 0.8618\n",
      "训练和验证耗费的时间5m56s\n",
      "Epoch 25/49\n",
      "----------\n",
      "25 train loss:0.2261 train acc: 0.9176\n",
      "25 val loss:0.2854 val acc: 0.8882\n",
      "训练和验证耗费的时间6m10s\n",
      "Epoch 26/49\n",
      "----------\n",
      "26 train loss:0.1848 train acc: 0.9325\n",
      "26 val loss:0.2638 val acc: 0.9243\n",
      "训练和验证耗费的时间6m25s\n",
      "Epoch 27/49\n",
      "----------\n",
      "27 train loss:0.1324 train acc: 0.9473\n",
      "27 val loss:0.2261 val acc: 0.9112\n",
      "训练和验证耗费的时间6m39s\n",
      "Epoch 28/49\n",
      "----------\n",
      "28 train loss:0.1283 train acc: 0.9539\n",
      "28 val loss:0.5604 val acc: 0.8421\n",
      "训练和验证耗费的时间6m53s\n",
      "Epoch 29/49\n",
      "----------\n",
      "29 train loss:0.1850 train acc: 0.9341\n",
      "29 val loss:0.3427 val acc: 0.9046\n",
      "训练和验证耗费的时间7m8s\n",
      "Epoch 30/49\n",
      "----------\n",
      "30 train loss:0.1865 train acc: 0.9325\n",
      "30 val loss:0.6925 val acc: 0.7500\n",
      "训练和验证耗费的时间7m22s\n",
      "Epoch 31/49\n",
      "----------\n",
      "31 train loss:0.2794 train acc: 0.8979\n",
      "31 val loss:0.5724 val acc: 0.8158\n",
      "训练和验证耗费的时间7m36s\n",
      "Epoch 32/49\n",
      "----------\n",
      "32 train loss:0.1778 train acc: 0.9415\n",
      "32 val loss:0.3561 val acc: 0.8914\n",
      "训练和验证耗费的时间7m50s\n",
      "Epoch 33/49\n",
      "----------\n",
      "33 train loss:0.1445 train acc: 0.9415\n",
      "33 val loss:0.4062 val acc: 0.8816\n",
      "训练和验证耗费的时间8m4s\n",
      "Epoch 34/49\n",
      "----------\n",
      "34 train loss:0.1581 train acc: 0.9456\n",
      "34 val loss:0.4252 val acc: 0.8947\n",
      "训练和验证耗费的时间8m18s\n",
      "Epoch 35/49\n",
      "----------\n",
      "35 train loss:0.1879 train acc: 0.9283\n",
      "35 val loss:0.2920 val acc: 0.9013\n",
      "训练和验证耗费的时间8m32s\n",
      "Epoch 36/49\n",
      "----------\n",
      "36 train loss:0.1782 train acc: 0.9325\n",
      "36 val loss:0.3149 val acc: 0.8914\n",
      "训练和验证耗费的时间8m46s\n",
      "Epoch 37/49\n",
      "----------\n",
      "37 train loss:0.1486 train acc: 0.9498\n",
      "37 val loss:0.5385 val acc: 0.8520\n",
      "训练和验证耗费的时间9m0s\n",
      "Epoch 38/49\n",
      "----------\n",
      "38 train loss:0.2116 train acc: 0.9217\n",
      "38 val loss:0.3824 val acc: 0.8849\n",
      "训练和验证耗费的时间9m14s\n",
      "Epoch 39/49\n",
      "----------\n",
      "39 train loss:0.2164 train acc: 0.9308\n",
      "39 val loss:0.3378 val acc: 0.8651\n",
      "训练和验证耗费的时间9m29s\n",
      "Epoch 40/49\n",
      "----------\n",
      "40 train loss:0.2061 train acc: 0.9325\n",
      "40 val loss:0.3562 val acc: 0.8816\n",
      "训练和验证耗费的时间9m43s\n",
      "Epoch 41/49\n",
      "----------\n",
      "41 train loss:0.1354 train acc: 0.9481\n",
      "41 val loss:0.3470 val acc: 0.9079\n",
      "训练和验证耗费的时间9m57s\n",
      "Epoch 42/49\n",
      "----------\n",
      "42 train loss:0.0586 train acc: 0.9769\n",
      "42 val loss:0.4022 val acc: 0.9013\n",
      "训练和验证耗费的时间10m12s\n",
      "Epoch 43/49\n",
      "----------\n",
      "43 train loss:0.1470 train acc: 0.9473\n",
      "43 val loss:0.2613 val acc: 0.9046\n",
      "训练和验证耗费的时间10m26s\n",
      "Epoch 44/49\n",
      "----------\n",
      "44 train loss:0.0662 train acc: 0.9786\n",
      "44 val loss:0.2345 val acc: 0.9408\n",
      "训练和验证耗费的时间10m40s\n",
      "Epoch 45/49\n",
      "----------\n",
      "45 train loss:0.0283 train acc: 0.9885\n",
      "45 val loss:0.2043 val acc: 0.9375\n",
      "训练和验证耗费的时间10m54s\n",
      "Epoch 46/49\n",
      "----------\n",
      "46 train loss:0.1299 train acc: 0.9448\n",
      "46 val loss:0.2280 val acc: 0.9276\n",
      "训练和验证耗费的时间11m8s\n",
      "Epoch 47/49\n",
      "----------\n",
      "47 train loss:0.1087 train acc: 0.9605\n",
      "47 val loss:0.2933 val acc: 0.9243\n",
      "训练和验证耗费的时间11m22s\n",
      "Epoch 48/49\n",
      "----------\n",
      "48 train loss:0.1426 train acc: 0.9506\n",
      "48 val loss:0.6825 val acc: 0.7993\n",
      "训练和验证耗费的时间11m36s\n",
      "Epoch 49/49\n",
      "----------\n",
      "49 train loss:0.0848 train acc: 0.9695\n",
      "49 val loss:0.2191 val acc: 0.9408\n",
      "训练和验证耗费的时间11m50s\n"
     ]
    },
    {
     "data": {
      "image/png": "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Nm0akpOTsXfvXgQHB2utDw4Ohq+vL1JSUjTLysvLceDAAfTr16++m6sbM+VEREQAAG9vbyiVylpZcUM303W55557cObMGb3rXV1d4enpqfWwOQblRERkI7IG5VOnTsVnn32GTZs2oXnz5sjPz0d+fj5KSkoAiDvt8fHxWLBgAb766iv89ttvmDhxIjw8PDB27Fg5m16FQTkREREAwMXFBWFhYVo30wEgJSXFpJvpGRkZWhVydkHdb51BORERWZms5evqkVijo6O1lq9btw4TJ04EAMycORMlJSWIi4vDlStXEBERgT179qB58+b13Fo9WL5ORESkkZCQgPHjxyM8PByRkZFYvXo1cnJyMGXKFACi9Dw3NxcbN24EACQlJaF9+/bo3r07ysvL8dlnn2H79u3Yvn27nF+jNmbKiYjIRmQNyiVJqnMbhUKBuXPnYu7cubZvkDmYKSciItIYPXo0Ll26hLfffht5eXkIDQ3Frl270K5dOwBAXl4ecnJyNNuXl5fjlVdeQW5uLtzd3dG9e3d88803GD58uFxfQTd1UF4fpfJERNSo2MVAbw0ag3IiIiItcXFxiIuL07lu/fr1Wu9nzpyJmTNn1kOrLFBZyfJ1IiKyGbuZEq3BUgflLF8nIiJyTNevA+rqPgblRERkZQzKLaXuU85MORERkWNSl647OQHu7vK2hYiIHA6DckuxfJ2IiMixVR/kTc9860REROZiUG4plq8TERE5No68TkRENsSg3FIsXyciInJsDMqJiMiGGJRbiuXrREREjo0jrxMRkQ0xKLcUg3IiIiLHxkw5ERHZEINyS6nL19mnnIiIyDGpg3JPT3nbQUREDolBuaWYKSciInJszJQTEZENMSi3FINyIiIix8agnIiIbIhBuaVYvk5EROTYGJQTEZENMSi3FDPlREREjo1BORER2RCDcksxKCciInJsDMqJiMiGGJRbSh2Us3ydiIjIMXGeciIisiEG5ZZS9ylnppyIiMgxMVNOREQ2xKDcUupMeWUlUFEhb1uIiIjI+jhPORER2RCDckupg3KAJexERESORpKYKSciIptiUG6p6kE5S9iJiIgcy82bgEolXjMoJyIiG2BQbiknJ0CpFK8ZlBMRETkWdZa8SROgWTN520JERA6JQbk1cAR2IiIix1S9P7lCIW9biIjIITEotwaOwE5EROSY2J+c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zZo1CwkJCZr3xcXFCAwMNL/BRER2QKlUWi3Io4aFQTkRERFZhbe3N5RKZa2seEFBQa3seU1btmzBpEmTsHXrVtx///0Gt3V1dYWrq6Gp9oiIiBqOJnI3gIiIiByDi4sLwsLCkJKSorU8JSUF/fr10/u5L774AhMnTsSmTZvw4IMP2rqZREREdoWZciIiIrKahIQEjB8/HuHh4YiMjMTq1auRk5ODKVOmABCl57m5udi4cSMAEZBPmDABy5cvxz333KPJsru7u8PLy0u270FERFRfHD4oV8/4xpFZiYjIXqivSY44K+no0aNx6dIlvP3228jLy0NoaCh27dqFdu3aARBT/+Tk5Gi2//jjj1FRUYGpU6di6tSpmuVPP/001q9fb9Qxea0nIiJ7Y8q13uHnKb9w4QIHfyEiIrt0/vx5BAQEyN2MBo/XeiIislfGXOsdPiivrKzExYsX0bx5c6NGfjVEPbrr+fPn65wAnrTx3JmH5818PHfm4Xkzj6nnTZIkXLt2Df7+/mjShMO7WMqa13qA/w7MxfNmHp438/HcmYfnzXymnDtTrvUOX77epEkTq2chPD09+QtsJp478/C8mY/nzjw8b+Yx5byxv7T12OJaD/Dfgbl43szD82Y+njvz8LyZz9hzZ+y1nrfniYiIiIiIiGTCoJyIiIiIiIhIJgzKTeDq6oq33noLrq6ucjelweG5Mw/Pm/l47szD82YenjfHwp+neXjezMPzZj6eO/PwvJnPVufO4Qd6IyIiIiIiIrJXzJQTERERERERyYRBOREREREREZFMGJQTERERERERyYRBOREREREREZFMGJSbYOXKlQgODoabmxvCwsKQmpoqd5PsysGDBzFixAj4+/tDoVBgx44dWuslScLcuXPh7+8Pd3d3REdH48SJE/I01o4kJibirrvuQvPmzdG2bVuMGjUKWVlZWtvw3Om2atUq9OzZE56envD09ERkZCS+/fZbzXqeN+MkJiZCoVAgPj5es4znTre5c+dCoVBoPXx9fTXred4aPl7r68brvel4rTcfr/XWwWu98eS41jMoN9KWLVsQHx+P2bNnIyMjA1FRURg2bBhycnLkbprduHHjBnr16oUVK1boXL9o0SIsXboUK1aswJEjR+Dr64shQ4bg2rVr9dxS+3LgwAFMnToVhw8fRkpKCioqKhATE4MbN25otuG50y0gIAALFy7E0aNHcfToUdx3330YOXKk5j9Gnre6HTlyBKtXr0bPnj21lvPc6de9e3fk5eVpHsePH9es43lr2HitNw6v96bjtd58vNZbjtd609X7tV4io9x9993SlClTtJZ17dpVev3112VqkX0DIH311Vea95WVlZKvr6+0cOFCzbLS0lLJy8tL+uijj2Roof0qKCiQAEgHDhyQJInnzlQtW7aUPv30U543I1y7dk3q3LmzlJKSIg0cOFCaMWOGJEn8nTPkrbfeknr16qVzHc9bw8drvel4vTcPr/WW4bXeeLzWm06Oaz0z5UYoLy9Heno6YmJitJbHxMQgLS1NplY1LNnZ2cjPz9c6h66urhg4cCDPYQ1FRUUAgFatWgHguTOWSqXC5s2bcePGDURGRvK8GWHq1Kl48MEHcf/992st57kz7MyZM/D390dwcDCefPJJ/PHHHwB43ho6Xuutg/8OjMNrvXl4rTcdr/Xmqe9rvZPFLW4ECgsLoVKp4OPjo7Xcx8cH+fn5MrWqYVGfJ13n8Ny5c3I0yS5JkoSEhAQMGDAAoaGhAHju6nL8+HFERkaitLQUzZo1w1dffYU777xT8x8jz5tumzdvxrFjx3DkyJFa6/g7p19ERAQ2btyILl264K+//sL8+fPRr18/nDhxguetgeO13jr476BuvNabjtd68/Babx45rvUMyk2gUCi03kuSVGsZGcZzaNi0adPw66+/4tChQ7XW8dzpFhISgszMTFy9ehXbt2/H008/jQMHDmjW87zVdv78ecyYMQN79uyBm5ub3u147mobNmyY5nWPHj0QGRmJjh07YsOGDbjnnnsA8Lw1dPz5WQfPo3681puO13rT8VpvPjmu9SxfN4K3tzeUSmWtO+UFBQW17pKQbuoRC3kO9XvxxRexc+dO7Nu3DwEBAZrlPHeGubi4oFOnTggPD0diYiJ69eqF5cuX87wZkJ6ejoKCAoSFhcHJyQlOTk44cOAAPvjgAzg5OWnOD89d3Zo2bYoePXrgzJkz/J1r4Hittw7+OzCM13rz8FpvOl7rrac+rvUMyo3g4uKCsLAwpKSkaC1PSUlBv379ZGpVwxIcHAxfX1+tc1heXo4DBw40+nMoSRKmTZuG5ORk7N27F8HBwVrree5MI0kSysrKeN4MGDx4MI4fP47MzEzNIzw8HE899RQyMzPRoUMHnjsjlZWV4dSpU/Dz8+PvXAPHa7118N+BbrzWWxev9XXjtd566uVab/YQcY3M5s2bJWdnZ2nNmjXSyZMnpfj4eKlp06bSn3/+KXfT7Ma1a9ekjIwMKSMjQwIgLV26VMrIyJDOnTsnSZIkLVy4UPLy8pKSk5Ol48ePS2PGjJH8/Pyk4uJimVsur3/84x+Sl5eXtH//fikvL0/zuHnzpmYbnjvdZs2aJR08eFDKzs6Wfv31V+mNN96QmjRpIu3Zs0eSJJ43U1QfkVWSeO70efnll6X9+/dLf/zxh3T48GHpoYcekpo3b665FvC8NWy81huH13vT8VpvPl7rrYfXeuPIca1nUG6CDz/8UGrXrp3k4uIi9e3bVzONBQn79u2TANR6PP3005IkiSkE3nrrLcnX11dydXWV7r33Xun48ePyNtoO6DpnAKR169ZptuG50+3ZZ5/V/Jts06aNNHjwYM1FWpJ43kxR80LNc6fb6NGjJT8/P8nZ2Vny9/eXYmNjpRMnTmjW87w1fLzW143Xe9PxWm8+Xuuth9d648hxrVdIkiSZn2cnIiIiIiIiInOxTzkRERERERGRTBiUExEREREREcmEQTkRERERERGRTBiUExEREREREcmEQTkRERERERGRTBiUExEREREREcmEQTkRERERERGRTBiUE5FN7d+/HwqFAlevXpW7KURERGQDvNYTWYZBOREREREREZFMGJQTERERERERyYRBOZGDkyQJixYtQocOHeDu7o5evXph27ZtAKrKzb755hv06tULbm5uiIiIwPHjx7X2sX37dnTv3h2urq5o3749lixZorW+rKwMM2fORGBgIFxdXdG5c2esWbNGa5v09HSEh4fDw8MD/fr1Q1ZWlm2/OBERUSPBaz1Rw8agnMjB/fOf/8S6deuwatUqnDhxAi+99BLGjRuHAwcOaLZ59dVX8f777+PIkSNo27YtHn74Ydy6dQuAuMA+8cQTePLJJ3H8+HHMnTsXc+bMwfr16zWfnzBhAjZv3owPPvgAp06dwkcffYRmzZpptWP27NlYsmQJjh49CicnJzz77LP18v2JiIgcHa/1RA2cREQO6/r165Kbm5uUlpamtXzSpEnSmDFjpH379kkApM2bN2vWXbp0SXJ3d5e2bNkiSZIkjR07VhoyZIjW51999VXpzjvvlCRJkrKysiQAUkpKis42qI/x/fffa5Z98803EgCppKTEKt+TiIioseK1nqjhY6acyIGdPHkSpaWlGDJkCJo1a6Z5bNy4EWfPntVsFxkZqXndqlUrhISE4NSpUwCAU6dOoX///lr77d+/P86cOQOVSoXMzEwolUoMHDjQYFt69uypee3n5wcAKCgosPg7EhERNWa81hM1fE5yN4CIbKeyshIA8M033+COO+7QWufq6qp1sa5JoVAAEP3U1K/VJEnSvHZ3dzeqLc7OzrX2rW4fERERmYfXeqKGj5lyIgd25513wtXVFTk5OejUqZPWIzAwULPd4cOHNa+vXLmC06dPo2vXrpp9HDp0SGu/aWlp6NKlC5RKJXr06IHKykqtfmtERERUP3itJ2r4mCkncmDNmzfHK6+8gpdeegmVlZUYMGAAiouLkZaWhmbNmqFdu3YAgLfffhutW7eGj48PZs+eDW9vb4waNQoA8PLLL+Ouu+7CO++8g9GjR+PHH3/EihUrsHLlSgBA+/bt8fTTT+PZZ5/FBx98gF69euHcuXMoKCjAE088IddXJyIiahR4rSdyAPJ2aSciW6usrJSWL18uhYSESM7OzlKbNm2koUOHSgcOHNAMzPJ///d/Uvfu3SUXFxfprrvukjIzM7X2sW3bNunOO++UnJ2dpaCgIGnx4sVa60tKSqSXXnpJ8vPzk1xcXKROnTpJa9eulSSpavCXK1euaLbPyMiQAEjZ2dm2/vpEREQOj9d6ooZNIUnVOowQUaOyf/9+DBo0CFeuXEGLFi3kbg4RERFZGa/1RPaPfcqJiIiIiIiIZMKgnIiIiIiIiEgmLF8nIiIiIiIikgkz5UREREREREQyYVBOREREREREJBMG5UREREREREQyYVBOREREREREJBMG5UREREREREQyYVBOREREREREJBMG5UREREREREQyYVBOREREREREJBMG5UREREREREQy+X+vLkQ4oVvCAgAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 1200x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import copy\n",
    "import time\n",
    "\n",
    "import torch\n",
    "from torchvision.datasets import ImageFolder\n",
    "from torchvision import transforms\n",
    "import torch.utils.data as Data\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from model import GoogLeNet, Inception\n",
    "import torch.nn as nn\n",
    "import pandas as pd\n",
    "\n",
    "def train_val_data_process():\n",
    "    # 定义数据集的路径\n",
    "    ROOT_TRAIN = r'./data/train'\n",
    "\n",
    "    normalize = transforms.Normalize([0.22890568,0.19639583,0.1433638 ], [0.09950783, 0.07997292, 0.06596899])\n",
    "    # 定义数据集处理方法变量\n",
    "    train_transform = transforms.Compose([transforms.Resize((224,224)), transforms.ToTensor(), normalize])\n",
    "    # 加载数据集\n",
    "    train_data = ImageFolder(ROOT_TRAIN, transform=train_transform)\n",
    "\n",
    "    train_data, val_data = Data.random_split(train_data, [round(0.8*len(train_data)), round(0.2*len(train_data))])\n",
    "    train_dataloader = Data.DataLoader(dataset=train_data,\n",
    "                                       batch_size=32,\n",
    "                                       shuffle=True,\n",
    "                                       num_workers=2)\n",
    "\n",
    "    val_dataloader = Data.DataLoader(dataset=val_data,\n",
    "                                       batch_size=32,\n",
    "                                       shuffle=True,\n",
    "                                       num_workers=2)\n",
    "\n",
    "    return train_dataloader, val_dataloader\n",
    "\n",
    "\n",
    "\n",
    "def train_model_process(model, train_dataloader, val_dataloader, num_epochs):\n",
    "    # 设定训练所用到的设备，有GPU用GPU没有GPU用CPU\n",
    "    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "    # 使用Adam优化器，学习率为0.001\n",
    "    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "    # 损失函数为交叉熵函数\n",
    "    criterion = nn.CrossEntropyLoss()\n",
    "    # 将模型放入到训练设备中\n",
    "    model = model.to(device)\n",
    "    # 复制当前模型的参数\n",
    "    best_model_wts = copy.deepcopy(model.state_dict())\n",
    "\n",
    "    # 初始化参数\n",
    "    # 最高准确度\n",
    "    best_acc = 0.0\n",
    "    # 训练集损失列表\n",
    "    train_loss_all = []\n",
    "    # 验证集损失列表\n",
    "    val_loss_all = []\n",
    "    # 训练集准确度列表\n",
    "    train_acc_all = []\n",
    "    # 验证集准确度列表\n",
    "    val_acc_all = []\n",
    "    # 当前时间\n",
    "    since = time.time()\n",
    "\n",
    "    for epoch in range(num_epochs):\n",
    "        print(\"Epoch {}/{}\".format(epoch, num_epochs-1))\n",
    "        print(\"-\"*10)\n",
    "\n",
    "        # 初始化参数\n",
    "        # 训练集损失函数\n",
    "        train_loss = 0.0\n",
    "        # 训练集准确度\n",
    "        train_corrects = 0\n",
    "        # 验证集损失函数\n",
    "        val_loss = 0.0\n",
    "        # 验证集准确度\n",
    "        val_corrects = 0\n",
    "        # 训练集样本数量\n",
    "        train_num = 0\n",
    "        # 验证集样本数量\n",
    "        val_num = 0\n",
    "\n",
    "        # 对每一个mini-batch训练和计算\n",
    "        for step, (b_x, b_y) in enumerate(train_dataloader):\n",
    "            # 将特征放入到训练设备中\n",
    "            b_x = b_x.to(device)\n",
    "            # 将标签放入到训练设备中\n",
    "            b_y = b_y.to(device)\n",
    "            # 设置模型为训练模式\n",
    "            model.train()\n",
    "\n",
    "            # 前向传播过程，输入为一个batch，输出为一个batch中对应的预测\n",
    "            output = model(b_x)\n",
    "            # 查找每一行中最大值对应的行标\n",
    "            pre_lab = torch.argmax(output, dim=1)\n",
    "            # 计算每一个batch的损失函数\n",
    "            loss = criterion(output, b_y)\n",
    "\n",
    "            # 将梯度初始化为0\n",
    "            optimizer.zero_grad()\n",
    "            # 反向传播计算\n",
    "            loss.backward()\n",
    "            # 根据网络反向传播的梯度信息来更新网络的参数，以起到降低loss函数计算值的作用\n",
    "            optimizer.step()\n",
    "            # 对损失函数进行累加\n",
    "            train_loss += loss.item() * b_x.size(0)\n",
    "            # 如果预测正确，则准确度train_corrects加1\n",
    "            train_corrects += torch.sum(pre_lab == b_y.data)\n",
    "            # 当前用于训练的样本数量\n",
    "            train_num += b_x.size(0)\n",
    "        for step, (b_x, b_y) in enumerate(val_dataloader):\n",
    "            # 将特征放入到验证设备中\n",
    "            b_x = b_x.to(device)\n",
    "            # 将标签放入到验证设备中\n",
    "            b_y = b_y.to(device)\n",
    "            # 设置模型为评估模式\n",
    "            model.eval()\n",
    "            # 前向传播过程，输入为一个batch，输出为一个batch中对应的预测\n",
    "            output = model(b_x)\n",
    "            # 查找每一行中最大值对应的行标\n",
    "            pre_lab = torch.argmax(output, dim=1)\n",
    "            # 计算每一个batch的损失函数\n",
    "            loss = criterion(output, b_y)\n",
    "\n",
    "\n",
    "            # 对损失函数进行累加\n",
    "            val_loss += loss.item() * b_x.size(0)\n",
    "            # 如果预测正确，则准确度train_corrects加1\n",
    "            val_corrects += torch.sum(pre_lab == b_y.data)\n",
    "            # 当前用于验证的样本数量\n",
    "            val_num += b_x.size(0)\n",
    "\n",
    "        # 计算并保存每一次迭代的loss值和准确率\n",
    "        # 计算并保存训练集的loss值\n",
    "        train_loss_all.append(train_loss / train_num)\n",
    "        # 计算并保存训练集的准确率\n",
    "        train_acc_all.append(train_corrects.double().item() / train_num)\n",
    "\n",
    "        # 计算并保存验证集的loss值\n",
    "        val_loss_all.append(val_loss / val_num)\n",
    "        # 计算并保存验证集的准确率\n",
    "        val_acc_all.append(val_corrects.double().item() / val_num)\n",
    "\n",
    "        print(\"{} train loss:{:.4f} train acc: {:.4f}\".format(epoch, train_loss_all[-1], train_acc_all[-1]))\n",
    "        print(\"{} val loss:{:.4f} val acc: {:.4f}\".format(epoch, val_loss_all[-1], val_acc_all[-1]))\n",
    "\n",
    "        if val_acc_all[-1] > best_acc:\n",
    "            # 保存当前最高准确度\n",
    "            best_acc = val_acc_all[-1]\n",
    "            # 保存当前最高准确度的模型参数\n",
    "            best_model_wts = copy.deepcopy(model.state_dict())\n",
    "\n",
    "        # 计算训练和验证的耗时\n",
    "        time_use = time.time() - since\n",
    "        print(\"训练和验证耗费的时间{:.0f}m{:.0f}s\".format(time_use//60, time_use%60))\n",
    "\n",
    "    # 选择最优参数，保存最优参数的模型\n",
    "    model.load_state_dict(best_model_wts)\n",
    "    # torch.save(model.load_state_dict(best_model_wts), \"C:/Users/86159/Desktop/LeNet/best_model.pth\")\n",
    "    torch.save(best_model_wts, \"./best_model.pth\")\n",
    "\n",
    "\n",
    "    train_process = pd.DataFrame(data={\"epoch\":range(num_epochs),\n",
    "                                       \"train_loss_all\":train_loss_all,\n",
    "                                       \"val_loss_all\":val_loss_all,\n",
    "                                       \"train_acc_all\":train_acc_all,\n",
    "                                       \"val_acc_all\":val_acc_all,})\n",
    "\n",
    "    return train_process\n",
    "\n",
    "\n",
    "def matplot_acc_loss(train_process):\n",
    "    # 显示每一次迭代后的训练集和验证集的损失函数和准确率\n",
    "    plt.figure(figsize=(12, 4))\n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.plot(train_process['epoch'], train_process.train_loss_all, \"ro-\", label=\"Train loss\")\n",
    "    plt.plot(train_process['epoch'], train_process.val_loss_all, \"bs-\", label=\"Val loss\")\n",
    "    plt.legend()\n",
    "    plt.xlabel(\"epoch\")\n",
    "    plt.ylabel(\"Loss\")\n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.plot(train_process['epoch'], train_process.train_acc_all, \"ro-\", label=\"Train acc\")\n",
    "    plt.plot(train_process['epoch'], train_process.val_acc_all, \"bs-\", label=\"Val acc\")\n",
    "    plt.xlabel(\"epoch\")\n",
    "    plt.ylabel(\"acc\")\n",
    "    plt.legend()\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    # 加载需要的模型\n",
    "    GoogLeNet = GoogLeNet(Inception)\n",
    "    # 加载数据集\n",
    "    train_data, val_data = train_val_data_process()\n",
    "    # 利用现有的模型进行模型的训练\n",
    "    train_process = train_model_process(GoogLeNet, train_data, val_data, num_epochs=50)\n",
    "    matplot_acc_loss(train_process)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 可能出现以下问题：\n",
    "---------------------------------------------------------------------------   <br>\n",
    "> FileNotFoundError                         Traceback (most recent call last) <br>\n",
    "> /tmp/ipykernel_51/3807826248.py in module  <br>\n",
    ">      206     GoogLeNet = GoogLeNet(Inception)   <br>\n",
    ">      207     # 加载数据集    <br>\n",
    ">  --> 208     train_data, val_data = train_val_data_process()   <br>\n",
    ">      209     # 利用现有的模型进行模型的训练   <br>\n",
    ">      210     train_process = train_model_process(GoogLeNet, train_data, val_data, num_epochs=50)   <br>\n",
    ">    <br>\n",
    ">  /tmp/ipykernel_51/3807826248.py in train_val_data_process()   <br>\n",
    ">      24     train_transform = transforms.Compose([transforms.Resize((224,224)), transforms.ToTensor(), normalize])   <br>\n",
    ">      25     # 加载数据集    <br>\n",
    "> ---> 26     train_data = ImageFolder(ROOT_TRAIN, transform=train_transform)    <br>\n",
    ">      27      <br>\n",
    ">      28     train_data, val_data = Data.random_split(train_data, [round(0.8*len(train_data)), round(0.2*len(train_data))])   <br>\n",
    ">      <br>\n",
    ">       <br>\n",
    "> FileNotFoundError: Found no valid file for the classes .ipynb_checkpoints. Supported extensions are: .jpg, .jpeg, .png, .ppm,  .bmp, .pgm, .tif, .tiff, .webp   <br>\n",
    "    \n",
    "    \n",
    "错误原因：隐藏文件夹干扰   .ipynb_checkpoints是Jupyter Notebook自动生成的隐藏文件夹，当数据集目录中存在该文件夹时，ImageFolder会误将其识别为图像分类的类别目录。但由于该目录中没有符合要求的图像文件，导致报错。\n",
    "    \n",
    "解决办法：删除掉隐藏文件夹，运行以下rm命令删除：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "'rm' �����ڲ����ⲿ���Ҳ���ǿ����еĳ���\n",
      "���������ļ���\n"
     ]
    }
   ],
   "source": [
    "!rm -rf /home/jovyan/data/train/.ipynb_checkpoints"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在运行下面model_test.py代码时如遇到类似问题，请删除./data/test/.ipynb_checkpoints文件夹。运行以下命令："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "'rm' �����ڲ����ⲿ���Ҳ���ǿ����еĳ���\n",
      "���������ļ���\n"
     ]
    }
   ],
   "source": [
    "!rm -rf /home/jovyan/data/test/.ipynb_checkpoints"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 运行model_test.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\13175\\AppData\\Local\\Temp\\ipykernel_15396\\3458636604.py:63: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load('best_model.pth', map_location=torch.device('cpu')))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试的准确率为： 0.927710843373494\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 香蕉 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 苹果 ------ 真实值： 葡萄\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 橘子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 香蕉 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 橘子 ------ 真实值： 橘子\n",
      "预测值： 葡萄 ------ 真实值： 葡萄\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 香蕉 ------ 真实值： 橘子\n",
      "预测值： 梨子 ------ 真实值： 香蕉\n",
      "预测值： 苹果 ------ 真实值： 苹果\n",
      "预测值： 香蕉 ------ 真实值： 香蕉\n",
      "预测值： 梨子 ------ 真实值： 梨子\n",
      "预测值： 苹果\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.utils.data as Data\n",
    "from torchvision import transforms\n",
    "from torchvision.datasets import FashionMNIST\n",
    "from model import GoogLeNet, Inception\n",
    "from torchvision.datasets import ImageFolder\n",
    "from PIL import Image\n",
    "\n",
    "def test_data_process():\n",
    "    # 定义数据集的路径\n",
    "    ROOT_TRAIN = r'./data/test'\n",
    "\n",
    "    normalize = transforms.Normalize([0.22890568,0.19639583,0.1433638 ], [0.09950783, 0.07997292, 0.06596899])\n",
    "    # 定义数据集处理方法变量\n",
    "    test_transform = transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(), normalize])\n",
    "    # 加载数据集\n",
    "    test_data = ImageFolder(ROOT_TRAIN, transform=test_transform)\n",
    "\n",
    "    test_dataloader = Data.DataLoader(dataset=test_data,\n",
    "                                       batch_size=1,\n",
    "                                       shuffle=True,\n",
    "                                       num_workers=0)\n",
    "    return test_dataloader\n",
    "\n",
    "\n",
    "def test_model_process(model, test_dataloader):\n",
    "    # 设定测试所用到的设备，有GPU用GPU没有GPU用CPU\n",
    "    device = \"cuda\" if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "    # 讲模型放入到训练设备中\n",
    "    model = model.to(device)\n",
    "\n",
    "    # 初始化参数\n",
    "    test_corrects = 0.0\n",
    "    test_num = 0\n",
    "\n",
    "    # 只进行前向传播计算，不计算梯度，从而节省内存，加快运行速度\n",
    "    with torch.no_grad():\n",
    "        for test_data_x, test_data_y in test_dataloader:\n",
    "            # 将特征放入到测试设备中\n",
    "            test_data_x = test_data_x.to(device)\n",
    "            # 将标签放入到测试设备中\n",
    "            test_data_y = test_data_y.to(device)\n",
    "            # 设置模型为评估模式\n",
    "            model.eval()\n",
    "            # 前向传播过程，输入为测试数据集，输出为对每个样本的预测值\n",
    "            output= model(test_data_x)\n",
    "            # 查找每一行中最大值对应的行标\n",
    "            pre_lab = torch.argmax(output, dim=1)\n",
    "            # 如果预测正确，则准确度test_corrects加1\n",
    "            test_corrects += torch.sum(pre_lab == test_data_y.data)\n",
    "            # 将所有的测试样本进行累加\n",
    "            test_num += test_data_x.size(0)\n",
    "\n",
    "    # 计算测试准确率\n",
    "    test_acc = test_corrects.double().item() / test_num\n",
    "    print(\"测试的准确率为：\", test_acc)\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    # 加载模型\n",
    "    model = GoogLeNet(Inception)\n",
    "    model.load_state_dict(torch.load('best_model.pth', map_location=torch.device('cpu')))\n",
    "    # # 利用现有的模型进行模型的测试\n",
    "    test_dataloader = test_data_process()\n",
    "    test_model_process(model, test_dataloader)\n",
    "\n",
    "    # 设定测试所用到的设备，有GPU用GPU没有GPU用CPU\n",
    "    device = \"cuda\" if torch.cuda.is_available() else 'cpu'\n",
    "    model = model.to(device)\n",
    "    classes = ['苹果', '香蕉', '葡萄', '橘子', '梨子']\n",
    "    with torch.no_grad():\n",
    "        for b_x, b_y in test_dataloader:\n",
    "            b_x = b_x.to(device)\n",
    "            b_y = b_y.to(device)\n",
    "\n",
    "            # 设置模型为验证模型\n",
    "            model.eval()\n",
    "            output = model(b_x)\n",
    "            pre_lab = torch.argmax(output, dim=1)\n",
    "            result = pre_lab.item()\n",
    "            label = b_y.item()\n",
    "            print(\"预测值：\",  classes[result], \"------\", \"真实值：\", classes[label])\n",
    "\n",
    "\n",
    "    image = Image.open('0189.jpg')\n",
    "\n",
    "    normalize = transforms.Normalize([0.22890568,0.19639583,0.1433638], [0.09950783, 0.07997292, 0.06596899])\n",
    "    # 定义数据集处理方法变量\n",
    "    test_transform = transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(), normalize])\n",
    "    image = test_transform(image)\n",
    "\n",
    "    # 添加批次维度\n",
    "    image = image.unsqueeze(0)\n",
    "\n",
    "    with torch.no_grad():\n",
    "        model.eval()\n",
    "        image = image.to(device)\n",
    "        output = model(image)\n",
    "        pre_lab = torch.argmax(output, dim=1)\n",
    "        result = pre_lab.item()\n",
    "    print(\"预测值：\",  classes[result])\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
